home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Sample Code / Macintosh Sample Code / SC.024.SoundApp / SoundUnit.p < prev   
Encoding:
Text File  |  1994-11-18  |  54.8 KB  |  1,536 lines  |  [TEXT/MPS ]

  1. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2. #
  3. # Apple Macintosh Developer Technical Support
  4. #
  5. # SoundUnit
  6. #
  7. # SoundUnit.p    - MPW 3.0/3.1 Pascal Source
  8. #
  9. # Versions:
  10. #             1.03                    May, 1990
  11. #            1.1b1                    Nov, 1990            MPW 3.2 update
  12. #
  13. # Components:
  14. #             SoundApp.make        May 1, 1990            MPW build script
  15. #             SoundApp.p            May 1, 1990            Pascal source code
  16. #             SoundApp.r            May 1, 1990            Rez source code
  17. #             SoundAppSnds.r        May 1, 1990            Rez source code
  18. #             SoundUnit.p            May 1, 1990            Pascal source code
  19. #
  20. # Version comments
  21. #
  22. #    1.1:  This is the "new" SoundUnit which adds some new features.
  23. #            • Some knowledge of the new Sound Manager is present
  24. #              in areas that were work-arounds for old Sound Manager bugs
  25. #            • Conversion to MPW 3.2 was established (with some amount of pain)
  26. #            • Added the new Sound Manager error strings
  27. #            • Checking of the sound header for supported encode values
  28. #            • The amp value is ignored (never used) in a freqDurationCmd
  29. #            • Added functions to test sound hardware/software features
  30. #              such as stereo, MACE, Sound Input
  31. #
  32. #
  33. # Formatting was done with FONT = Monaco, SIZE = 9, TABS = 3
  34. #
  35. #
  36. # SoundApp.p is a sample application source file for demonstrating the
  37. # Sound Manager.  This portion of the source code handles the Sound Manager
  38. # part of the application.  This UNIT can be used by others.
  39. #
  40. # Jim Reekes E.O., Macintosh Developer Technical Support
  41. # Tuesday, January 30, 1990  1:01 PM
  42. #
  43. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  44.  
  45. UNIT SoundUnit;
  46.  
  47. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  48. INTERFACE
  49. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  50.  
  51. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  52. USES
  53. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  54. {Only include the interface files that I really need.  This helps MPW
  55.  to compile faster.}
  56.  
  57.     Types, Traps, Memory, Resources, OSUtils, Errors, FixMath, FP, Sound,
  58.     SoundInput, GestaltEqu;
  59.  
  60. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  61. CONST
  62. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  63.  
  64. {refer to the SoundAppSnds.r file for documentation on these values}
  65.     kOctave1 =            0;                    {octaves of MIDI values}
  66.     kOctave2 =            12;
  67.     kOctave3 =            24;
  68.     kOctave4 =            36;
  69.     kOctave5 =            48;
  70.     kOctave6 =            60;
  71.     kOctave7 =            72;
  72.     kOctave8 =            84;
  73.     kOctave9 =            96;
  74.     kOctave10 =            108;
  75.     kOctave11 =            120;
  76.  
  77.     Akey =                -3;                {the key A}
  78.     Bbkey =                -2;                {the key B flat}
  79.     Bkey =                -1;                {the key B}
  80.     Ckey =                0;                    {the key C}
  81.     Dbkey =                1;                    {the key D flat}
  82.     Dkey =                2;                    {the key D}
  83.     Ebkey =                3;                    {the key D flat}
  84.     Ekey =                4;                    {the key E}
  85.     Fkey =                5;                    {the key F}
  86.     Gbkey =                6;                    {the key G flat}
  87.     Gkey =                7;                    {the key G}
  88.     Abkey =                8;                    {the key A flat}
  89.  
  90. {These are other constants used in the SoundUnit}
  91.     kInitNone =            0;                    {no init options}
  92.     kWait =                FALSE;            {wait for the channel}
  93.     kSMAsynch =            TRUE;                {asynchronous Sound Manager call}
  94.     kWaveSize =            512;                {standard size of wave table}
  95.     kSyncID =            $12345678;        {identifier used in syncCmd}
  96.     kOneSecond =        2000;                {one second frequency duration}
  97.  
  98. {These are used as flags in the sound channel to determine the state
  99.  of that channel.  The 'snth' IDs are used when the channel is in use
  100.  to determine what that channel in intended for.}
  101.     kNoSynth =            0;                    {no synth is specified}
  102.     kChanComplete =    -1;                {channel has completed}
  103.     kChanFree =            MAXLONGINT;        {channel is not in use}
  104.     kSoundComplete =    $1234;            {flag for callBackCmd}
  105.  
  106. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  107. TYPE
  108. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  109.  
  110.     SndCmdPtr =            ^SndCommand;    {Ptr to a sound command, for type coersion}
  111.     IntPtr =                ^INTEGER;        {Ptr to a INTEGER, for type coersion}
  112.  
  113. {I have declared a few TYPEs below to help examine 'snd ' resources.
  114.  I have to break them up into individual pieces because they are
  115.  variable sized records.}
  116.  
  117.     Snd1Header =        RECORD
  118.         format:            INTEGER;
  119.         numSynths:        INTEGER;
  120.     END;
  121.     Snd1HdrPtr =        ^Snd1Header;
  122.     Snd1HdrHndl =        ^Snd1HdrPtr;
  123.  
  124.     SynthInfo =            RECORD
  125.         synthID:            INTEGER;
  126.         initOption:        LONGINT;
  127.     END;
  128.     SynthInfoPtr =        ^SynthInfo;
  129.  
  130.     Snd2Header =        RECORD
  131.         format:            INTEGER;
  132.         refCount:        INTEGER;
  133.     END;
  134.     Snd2HdrPtr =        ^Snd2Header;
  135.     Snd2HdrHndl =        ^Snd2HdrPtr;
  136.  
  137. {I have created my own sound channel type.  It extends the normal
  138.  sound channel by adding a few fields.  To confirm that the channel
  139.  in question is mine, I set the userInfo field to something that I
  140.  can recognize.  I keep the 'snd ' resource handle associated to this
  141.  channel too.  This allows me to dispose of the data once the channel
  142.  has completed its duties.}
  143.  
  144.     MyChanType =        RECORD                {this is a 1064 byte structure}
  145.         theChan:            SndChannel;            {must keep sound channel as first field}
  146.         dataHandle:        Handle;
  147.     END;
  148.     MyChanPtr =            ^MyChanType;
  149.  
  150.  
  151. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  152. VAR {The “g” prefix is used to emphasize that a variable is global.}
  153. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  154.  
  155. {These four global pointers are to my sound channels.  One of them has to
  156.  be global and I could utilize the fact that they are a linked list.  That
  157.  would complicate things to some degree, and it really wouldn’t have that
  158.  much of an advantage.}
  159.  
  160.     gChan1:             MyChanPtr;
  161.     gChan2:             MyChanPtr;
  162.     gChan3:             MyChanPtr;
  163.     gChan4:             MyChanPtr;
  164.  
  165. {This is the global flag that is set once the sound channel’s call back
  166.  procedure has been called.  This, in my case, means the channel has
  167.  completed its duties and is time for disposing.}
  168.  
  169.     gCalledBack:    BOOLEAN;
  170.  
  171. {gChanOpen is a flag set to determine if the application has a sound
  172.  channel open.  It’s really not feasible to determine if a sound is being
  173.  made at any given point.}
  174.  
  175.      gChanOpen:        BOOLEAN;
  176.  
  177. {gNewSndMgr is a flag used to determine if the application is running
  178.  with the new Sound Manager.  This was initially shipped in System 6.0.6.
  179.  This flag is setup in the InitSoundUnit routine and used by the rest of this
  180.  UNIT.  This is new for version 1.1}
  181.  
  182.     gNewSndMgr:        BOOLEAN;
  183.  
  184. {gHasMACE is a flag used to determine if the MACE compression/decompression
  185.  routines are available.  It is necessary for MACE to be present before using
  186.  a compressed sound.  This is new for version 1.1}
  187.  
  188.     gHasMACE:        BOOLEAN;
  189.  
  190. {gHasSndInput is a flag used to determine if the Sound Input Manager is
  191.  available.  This is new for version 1.1}
  192.  
  193.     gHasSndInput:    BOOLEAN;
  194.  
  195. {gHasStereo is a flag used to determine if stereo hardware is present.
  196.  This is new for version 1.1}
  197.  
  198.     gHasStereo:    BOOLEAN;
  199.  
  200.  
  201.  
  202. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  203.  
  204. {The routines below are for public consumption in this UNIT.  Note that
  205.  these all return standard sound channels to allow easy of modifications.
  206.  I can change the structure of my own sound channels and the code without
  207.  forcing a change in the application that uses this UNIT.
  208.  
  209.  VERSION 1.1: No longer putting the SANELib into a seperate unit, which then
  210.  needs to be unloaded.  Instead, I merge it into the Main segment.  Refer to
  211.  the Make file for more information.}
  212.  
  213. PROCEDURE _SoundUnit;
  214.  
  215. FUNCTION InitSoundUnit: OSErr;
  216. FUNCTION HasNewSndMgr: BOOLEAN;
  217. FUNCTION HasMACE: BOOLEAN;
  218. FUNCTION HasSoundInput: BOOLEAN;
  219. FUNCTION HasStereo: BOOLEAN;
  220. FUNCTION SoundCompletion: BOOLEAN;
  221. FUNCTION SndChanOpen: BOOLEAN;
  222. FUNCTION SetSquareTimbre(squareChan: SndChannelPtr; timbre: INTEGER;
  223.                                 immediate: BOOLEAN): OSErr;
  224. FUNCTION SendFreqDur(chan: SndChannelPtr; duration: INTEGER; freq: LONGINT): OSErr;
  225. FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
  226. FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
  227. PROCEDURE DoSoundComplete;
  228. PROCEDURE FreeAllChans;
  229. PROCEDURE FreeSoundUnit;
  230. FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
  231. FUNCTION HoldSnd(sndHandle: Handle): OSErr;
  232. FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
  233. FUNCTION GetSndDataOffset(sndHandle: Handle;
  234.                                     VAR dataType, waveLength: INTEGER): LONGINT;
  235. FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
  236.                                                         sndInstrument: Handle): OSErr;
  237. FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
  238.                             waveLength: INTEGER): OSErr;
  239. FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
  240.                                   waveChan3, waveChan4: SndChannelPtr): OSErr;
  241. FUNCTION GetSquareChan(VAR squareChan: SndChannelPtr; timbre: INTEGER): OSErr;
  242. FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
  243. FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
  244. FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
  245. FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
  246.                             song1, song2, song3, song4: Handle): OSErr;
  247. FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
  248. FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
  249.  
  250.  
  251. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  252. IMPLEMENTATION
  253. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  254. {$S SoundUnit}
  255. PROCEDURE _SoundUnit;
  256.  
  257. {This is a dummy routine to allow the application to unload this UNIT.}
  258.  
  259. BEGIN
  260. END;
  261.  
  262. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  263. {$S Initialization}
  264. FUNCTION InitSoundUnit: OSErr;
  265.  
  266. {Create storage for each of the four channels (4 * 1064 bytes) and
  267.  initialize them.  If any of the channels cannot be allocated, return an
  268.  error.  Also initialized our global flag “gCalledBack.” An interesting
  269.  modification would be to allow the caller to pass in the number of
  270.  channels the application really intends on using. If the user only wants
  271.  one channel, then we could just allocate one instead of four.  These
  272.  channels are used at interrupt time.
  273.  
  274.  Version 1.1: Added the new Sound Manager flag, gNewSndMgr.  First I have to
  275.  test if the _SoundDispatch trap is available, since SndManagerVersion is
  276.  a selector for _SoundDispatch.  This this trap isn't available, then calling
  277.  SndManagerVersion would result in an unimplemented instruction.  If an error 
  278.  is returned, it is the old Sound Manager.  If it is zero, then the call 
  279.  is available (via-MIDI Mgr?) but it isn't the new Sound Manager.  Greater 
  280.  than zero means it is the new Sound Manager.  To determine if MACE is
  281.  available, there are two case.  If the new Sound Manager is running then
  282.  MACE may be built in.  This is easy enough to test for by calling the new
  283.  trap call.  Otherwise I have to test for the presence of the MACE
  284.  snth resources.  Also new is a flag to determine if Sound Input is available.  
  285.  The Gestalt call in System 6.0.7 will only report if the machine has 
  286.  Apple built in Sound Input hardware.  Other third parties may include an 
  287.  external device which will allow recording.  To determine if such a 
  288.  device is present, I use SPBGetIndexdDevice to find the fist one.  If 
  289.  this returns noErr then Sound Input is available. Also, the icon handle 
  290.  returned has to be disposed of.  The MACE snths are only for the old
  291.  Sound Manager which did not have built-in MACE.  They could only be present
  292.  if the user ran the MACE Installer Scripts which are available from APDA.}
  293.  
  294.     
  295.     FUNCTION InitMyChan(VAR newChan: MyChanPtr): BOOLEAN;
  296.     BEGIN
  297.         newChan:= MyChanPtr(NewPtrClear(SizeOf(MyChanType)));
  298.         IF newChan <> NIL THEN BEGIN
  299.             newChan^.theChan.qLength:= stdQLength;
  300.             newChan^.theChan.userInfo:= kChanFree;
  301.             InitMyChan:= TRUE;
  302.         END ELSE
  303.             InitMyChan:= FALSE;
  304.     END;
  305.  
  306. BEGIN
  307.     gChanOpen:= FALSE;
  308.     gCalledBack:= FALSE;
  309.     IF InitMyChan(gChan1) THEN
  310.         IF InitMyChan(gChan2) THEN
  311.             IF InitMyChan(gChan3) THEN
  312.                 IF InitMyChan(gChan4) THEN;
  313.     InitSoundUnit:= MemError;
  314.  
  315.     gNewSndMgr:= HasNewSndMgr;        {is the "new" Sound Manager running?}
  316.     gHasMACE:= HasMACE;                {is MACE (audio compression/expansion) running?}
  317.     gHasSndInput:= HasSoundInput;    {is the Sound Input Manager running?}
  318.     gHasStereo:= HasStereo;            {is there stereo support on this machine?}
  319. END;
  320.  
  321. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  322. {$S Main}
  323. FUNCTION HasNewSndMgr: BOOLEAN;
  324.  
  325. VAR
  326.     response:        LONGINT;
  327.  
  328. {VERSION 1.1:  This is the external routine for users to determine if
  329.  the new Sound Manager is available.}
  330.  
  331. BEGIN
  332.     IF noErr = Gestalt(gestaltSoundAttr, response) THEN
  333.         HasNewSndMgr:= BTst(response, gestaltSoundIOMgrPresent)
  334.     ELSE
  335.         HasNewSndMgr:= FALSE;
  336. END;
  337.  
  338. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  339. {$S Main}
  340. FUNCTION HasMACE: BOOLEAN;
  341.  
  342. VAR
  343.     devName:            Str255;
  344.     environRec:        SysEnvRec;
  345.     theCmd:            SndCommand;
  346.     response:        LONGINT;
  347.  
  348. {VERSION 1.1:  This is the external routine for users to determine if
  349.  audio compression/expansion is available.}
  350.  
  351. BEGIN
  352.     IF HasNewSndMgr THEN
  353.         HasMACE:= MACEVersion.majorRev > 0            {is the built-in MACE here?}
  354.     ELSE BEGIN
  355.         HasMACE:= FALSE    ;                                {default to no MACE}
  356.         theCmd.cmd:= versionCmd;                        {use versionCmd to check}
  357.         theCmd.param1:= 0;
  358.         theCmd.param2:= 0;
  359.         IF noErr = SndControl(MACE3snthID, theCmd) THEN    {if no errors, then...}            
  360.             IF noErr = SndControl(MACE6snthID, theCmd) THEN
  361.                 HasMACE:= TRUE;                            {we got MACE}
  362.     END;
  363. END;
  364.  
  365. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  366. {$S Main}
  367. FUNCTION HasSoundInput: BOOLEAN;
  368.  
  369. VAR
  370.     devName:            Str255;
  371.     devIconHandle:    Handle;
  372.  
  373. {VERSION 1.1:  This is the external routine for users to determine if
  374.  sound input is available.}
  375.  
  376. BEGIN
  377.     HasSoundInput:= FALSE;
  378.     IF HasNewSndMgr THEN BEGIN
  379.         IF SPBVersion.majorRev > 0 THEN BEGIN
  380.             IF SPBGetIndexedDevice(1, devName, devIconHandle) = noErr THEN BEGIN
  381.                 DisposeHandle(devIconHandle);
  382.                 HasSoundInput:= TRUE;
  383.             END;
  384.         END;
  385.     END;
  386. END;
  387.  
  388. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  389. {$S Main}
  390. FUNCTION HasStereo: BOOLEAN;
  391.  
  392. VAR
  393.     response:        LONGINT;
  394.  
  395. {VERSION 1.1:  This is the external routine for users to determine if
  396.  sound input is available.}
  397.  
  398. BEGIN
  399.     IF noErr = Gestalt(gestaltSoundAttr, response) THEN
  400.         HasStereo:= BTst(response, gestaltStereoCapability)
  401.     ELSE
  402.         HasStereo:= FALSE;
  403. END;
  404.  
  405. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  406. {$S Main}
  407. FUNCTION SoundCompletion: BOOLEAN;
  408.  
  409. {This routine can be called to determine if the sound has completed.  When
  410.  this is true, the sound data can be disposed of.  The global “gCalledBack”
  411.  determines whether the sound has completed or not and it is set by the
  412.  sound channel’s completion routine.  Soundcompletion is placed in the Main
  413.  segment because it is called by the event loop.}
  414.  
  415. BEGIN
  416.     SoundCompletion:= gCalledBack;
  417. END;
  418.  
  419. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  420. {$S Main}
  421. FUNCTION SndChanOpen: BOOLEAN;
  422.  
  423. {This routine can be called at any time.  It will return TRUE when the
  424.  SoundUnit has an open channel.  This can be can considered the same as
  425.  sound being active.  As long as the channel is open, no other channels can
  426.  be opened.  It is placed in the Main segment because it is called by the
  427.  event loop.}
  428.  
  429. BEGIN
  430.     SndChanOpen:= gChanOpen;
  431. END;
  432.  
  433. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  434. {$S SoundUnit}
  435. FUNCTION SetSquareTimbre(squareChan: SndChannelPtr; timbre: INTEGER;
  436.                                 immediate: BOOLEAN): OSErr;
  437.  
  438. {Given a channel and timbre (sounds like “tom burr”), this will adjust the
  439.  tone quality of the square wave synthesizer.  Changing the tone can only
  440.  be done before playing a frequency.  On a Mac with the Apple Sound Chip, this
  441.  can be done in real time while a sound is playing.  But, since there’s no
  442.  supported method for determining if the ASC is available I have to assume
  443.  that it’s not.  I use the immediate flag to determine if the user wants to
  444.  change the timbre now, or queue the command.  If the queue is full, it
  445.  will wait for the command to be accepted.
  446.  
  447.  BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
  448.  SE where sending a timbreCmd with a timbre of 255 (a legal value) will
  449.  crash.  The difference between 254 and 255 isn’t audible, so I only allow
  450.  a maximum of 254 in any case.}
  451.  
  452. VAR
  453.     theCmd:        SndCommand;
  454.  
  455. BEGIN
  456.     IF timbre > 254 THEN
  457.         timbre:= 254;
  458.     WITH theCmd DO BEGIN
  459.         cmd:= timbreCmd;
  460.         param1:= timbre;
  461.         param2:= 0;
  462.     END;
  463.     IF immediate THEN
  464.         SetSquareTimbre:= SndDoImmediate(squareChan, theCmd)
  465.     ELSE
  466.         SetSquareTimbre:= SndDoCommand(squareChan, theCmd, kWait);
  467. END;
  468.  
  469. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  470. {$S SoundUnit}
  471. FUNCTION SendFreqDur(chan: SndChannelPtr; duration: INTEGER;
  472.                                 freq: LONGINT): OSErr;
  473.  
  474. {Given a channel and frequency/duration information, this will place the
  475.  frequency into the channel’s queue.  The freq is a three-byte parameter
  476.  with the high byte being ignored.  I use SndDoCommand with the noWait
  477.  flag set to wait for the channel to except the command in case the queue
  478.  is currently full.}
  479.  
  480. VAR
  481.     theCmd:        SndCommand;
  482.  
  483. BEGIN
  484.     WITH theCmd DO BEGIN
  485.         cmd:= freqDurationCmd;
  486.         param1:= duration;
  487.         param2:= freq;
  488.     END;
  489.     SendFreqDur:= SndDoCommand(chan, theCmd, kWait);
  490. END;
  491.  
  492. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  493. {$S SoundUnit}
  494. FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
  495.  
  496. {Given a channel, this will place a quietCmd into the channel’s queue.  I
  497.  use SndDoCommand with the noWait flag set to wait for the channel to
  498.  accept the command in the case it is currently full.
  499.  
  500.  BUG NOTE: A sequence of frequencies and rests will not work unless quietCmds
  501.  are between them.  Rests have to be made quiet before they rest, if that
  502.  makes any more sense.  A freqDurationCmd will loop, causing the sound in
  503.  progress to continue, until a quietCmd is received.}
  504.  
  505. VAR
  506.     theCmd:        SndCommand;
  507.  
  508. BEGIN
  509.     WITH theCmd DO BEGIN
  510.         cmd:= quietCmd;
  511.         param1:= 0;
  512.         param2:= 0;
  513.     END;
  514.     IF immediate THEN
  515.         SendQuiet:= SndDoImmediate(chan, theCmd)
  516.     ELSE
  517.         SendQuiet:= SndDoCommand(chan, theCmd, kWait);
  518. END;
  519.  
  520. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  521. {$S SoundUnit}
  522. FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
  523.  
  524. {Given a channel and duration, this will place the rest into the channel’s
  525.  queue.  Before sending a rest a quietCmd is needed.  Rests don’t work
  526.  unless you tell the Sound Manager to be quiet too.  I use SndDoCommand
  527.  with the noWait flag set to wait for the channel to accept the command in
  528.  case it is currently full.}
  529.  
  530. VAR
  531.     theCmd:        SndCommand;
  532.     theErr:        OSErr;
  533.  
  534. BEGIN
  535.     theErr:= SendQuiet(chan, kWait);
  536.     IF theErr = noErr THEN BEGIN
  537.         WITH theCmd DO BEGIN
  538.             cmd:= restCmd;
  539.             param1:= duration;
  540.             param2:= 0;
  541.         END;
  542.         theErr:= SndDoCommand(chan, theCmd, kWait);
  543.     END;
  544.     SendRest:= theErr;
  545. END;
  546.  
  547. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  548. {$S SoundUnit}
  549. PROCEDURE FreeChan(myChan: MyChanPtr);
  550.  
  551. {Test if the channel is free, and if not then call SndDisposeChannel.
  552.  This will release the synthesizer (snth resource) code and the
  553.  required hardware.  If we didn’t do this, no other channels would
  554.  work.  I also test myChan for having a snd resource attached to the
  555.  channel.  If so, then I mark it as purgeable and reset the data to NIL.
  556.  
  557.  BUG NOTE: Calling SndDisposeChannel while or immediately after playing
  558.  a sequence of frequencies would often hang/crash a non-Apple Sound Chip based Mac.
  559.  Issuing a quietCmd first kept the Sound Manager happy and my Mac from
  560.  crashing.}
  561.  
  562. VAR
  563.     theErr:         OSErr;
  564.  
  565. BEGIN
  566.     IF myChan^.theChan.userInfo <> kChanFree THEN BEGIN
  567.         theErr:= SendQuiet(SndChannelPtr(myChan), NOT kWait);        {ignore error}
  568.         theErr:= SndDisposeChannel(SndChannelPtr(myChan), NOT kWait);
  569.         myChan^.theChan.userInfo:= kChanFree;
  570.     END;
  571.     IF myChan^.dataHandle <> NIL THEN BEGIN
  572.         HUnlock(myChan^.dataHandle);
  573.         HPurge(myChan^.dataHandle);
  574.         myChan^.dataHandle:= NIL;
  575.     END;
  576. END;
  577.  
  578. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  579. {$S SoundUnit}
  580. PROCEDURE DoSoundComplete;
  581.  
  582. {This routine is called by an application that established a sound to be
  583.  played asynchronously.  This is, in effect, the routine to be used after
  584.  the completion routine has been called.  The application should call this
  585.  once SoundCompletion returns TRUE.  In the case the application is using
  586.  multiple channels, we will only free a channel once it has been marked as
  587.  kChanComplete.  I do not reset the gCalledBack until all the open channels
  588.  are freed.}
  589.  
  590. BEGIN
  591.     IF gChan1^.theChan.userInfo = kChanComplete THEN
  592.         FreeChan(gChan1);
  593.     IF gChan2^.theChan.userInfo = kChanComplete THEN
  594.         FreeChan(gChan2);
  595.     IF gChan3^.theChan.userInfo = kChanComplete THEN
  596.         FreeChan(gChan3);
  597.     IF gChan4^.theChan.userInfo = kChanComplete THEN
  598.         FreeChan(gChan4);
  599.  
  600.     IF (gChan1^.theChan.userInfo = kChanFree)
  601.      & (gChan2^.theChan.userInfo = kChanFree)
  602.      & (gChan3^.theChan.userInfo = kChanFree)
  603.      & (gChan4^.theChan.userInfo = kChanFree) THEN BEGIN
  604.          gCalledBack:= FALSE;
  605.         gChanOpen:= FALSE;                                {no longer making noises}
  606.     END;
  607. END;
  608.  
  609. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  610. {$S SoundUnit}
  611. PROCEDURE FreeAllChans;
  612.  
  613. {This is the routine that will force all channels to be released.  It also
  614.  resets the gCalledBack flag.  This is used by all routines just before
  615.  opening a new channel to force all channels to be disposed.}
  616.  
  617. BEGIN
  618.     FreeChan(gChan1);
  619.     FreeChan(gChan2);
  620.     FreeChan(gChan3);
  621.     FreeChan(gChan4);
  622.     gCalledBack:= FALSE;
  623.     gChanOpen:= FALSE;                                {no longer making noises}
  624. END;
  625.  
  626. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  627. {$S SoundUnit}
  628. PROCEDURE FreeSoundUnit;
  629.  
  630. {This is the final routine to be called by the application when it is has
  631.  finished using this unit.  This will dispose of all the channels and
  632.  memory used by this unit.}
  633.  
  634. BEGIN
  635.     FreeAllChans;
  636.     IF gChan1 <> NIL THEN
  637.         DisposePtr(Ptr(gChan1));
  638.     IF gChan2 <> NIL THEN
  639.         DisposePtr(Ptr(gChan2));
  640.     IF gChan3 <> NIL THEN
  641.         DisposePtr(Ptr(gChan3));
  642.     IF gChan4 <> NIL THEN
  643.         DisposePtr(Ptr(gChan4));
  644.     gCalledBack:= FALSE;
  645. END;
  646.  
  647. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  648. {$S Main}
  649. PROCEDURE DoCallBack(chan: SndChannelPtr; theCmd: SndCommand);
  650.  
  651. {This will be called at interrupt time by the Sound Manager when it
  652.  receives a callBackCmd.  I use the second parameter of the command to hold
  653.  my application’s A5 reference.  I first set up A5 so that I can access my
  654.  globals.  I mark the given channel as being complete and set gCalledBack
  655.  to TRUE.  This lets the application know that the callBackCmd has been
  656.  processed.  The callBackCmd can be used for other purposes, and the first
  657.  parameter of the command could be a flag to a more extensive routine.
  658.  Synchronizing the application with the channel is possible with this
  659.  method.
  660.  
  661.  WARNING: This routine MUST be resident in memory and cannot make a call
  662.  to a non-resident segment.  I put this into the Main segment because of
  663.  this.
  664.  
  665.  BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
  666.  'snd '.  A bogus callBackCmd is placed into the queue immediately after
  667.  the bufferCmd used to play the sound.  This bogus callBackCmd will cause
  668.  my callBackProc to be called when I wasn’t expecting it.  I have been
  669.  using the command’s second parameter to contain my A5 address.  If I’m
  670.  given a bogus callBackCmd, it would be really bad to set A5 address to
  671.  this bogus parameter in the command.  I found that the bogus callBackCmd
  672.  contains the handle to the 'snd ' passed in to _SndPlay.  I also found
  673.  that param1 contains the handle’s state bits (results of HGetState).  To
  674.  work with this bug I set my real callBackCmd’s param1 to a specific value
  675.  when I installed it into the queue.  See the SoundComplete routine.  Then
  676.  I test the callBackCmd to make sure I’m dealing with the real one.}
  677.  
  678. VAR
  679.     theA5:         LONGINT;
  680.  
  681. BEGIN
  682.     IF thecmd.param1 = kSoundComplete THEN BEGIN        {if it’s my callBackCmd}
  683.         theA5:= SetA5(theCmd.param2);                     {refer to tech note 208}
  684.         chan^.userInfo:= kChanComplete;                    {this channel is done}
  685.         gCalledBack:= TRUE;
  686.         theA5:= SetA5(theA5);                                {restore original A5}
  687.     END;
  688. END;
  689.  
  690. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  691. {$S SoundUnit}
  692. FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
  693.  
  694. {I use this to install the callBackCmd into the given channel.  I need to
  695.  pass in our A5 along with the command so that the callBack routine can
  696.  access my globals.  I also wait until the channel is ready for another
  697.  command in the case of the channel being full.  Once the Sound Manager
  698.  calls my call back procedure I will dispose of the channel.  So, this is
  699.  the last sound command to be sent to a channel.  I pass to the call back
  700.  A5 in the second parameter of the callBackCmd.  Refer to Tech Note #208.}
  701.  
  702. VAR
  703.     theCmd:        SndCommand;
  704.  
  705. BEGIN
  706.     WITH theCmd DO BEGIN
  707.         cmd:= callBackCmd;
  708.         param1:= kSoundComplete;
  709.         param2:= SetCurrentA5;
  710.     END;
  711.     SoundComplete:= SndDoCommand(chan, theCmd, kWait);
  712. END;
  713.  
  714. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  715. {$S SoundUnit}
  716. FUNCTION ChanAvailable(theChan: SndChannelPtr): OSErr;
  717.  
  718. {This routine will test the given channel to see if it will really produce
  719.  sound.  The Sound Manager in System 6.0x will return noErr even if the
  720.  channel isn’t going to work.  In the future the Sound Manager will return
  721.  the proper error.  Until then, I use this routine to determine this for me.
  722.  There can only be a single channel at any time, unless I have the wave
  723.  table synthesizer open.  This will allow four channels.  Channels have
  724.  a pointer to the next channel, and if this is not NIL I suspect the
  725.  given channel will not work.  I test the given channel for being a
  726.  wave type, and if so I need to see if the other channels I’ve got are
  727.  also wave type.  If it doesn’t look like the channels is available, I
  728.  return badChannel.  It is important to set the userInfo field of a channel
  729.  before calling this routine!
  730.  
  731.  This routine assumes theChan passed in is the first channel allocated.
  732.  Channels are held in a linked list, and the first one to be tested has
  733.  to be the first one allocated.
  734.  
  735.  BUG NOTE: If an application is not using the Sound Manager and instead
  736.  uses the older Sound Driver, any given channel will fail.  Or if the other
  737.  application does not release is channels, then my channels will not work.
  738.  The most noticeable offender of this is HyperCard.  Friendly applications
  739.  will dispose of their channels at suspend/resume times or ASAP.
  740.  
  741.  Version 1.1: Added the new Sound Manager test.  The new Sound Manager will
  742.  allow multiple sound channels, and returns proper error codes.}
  743.  
  744.     FUNCTION IsMyChan(chan: SndChannelPtr): BOOLEAN;
  745.     BEGIN
  746.         IsMyChan:= ((chan = SndChannelPtr(gChan1))         {is it one of ours?}
  747.                     | (chan = SndChannelPtr(gChan2))
  748.                     | (chan = SndChannelPtr(gChan3))
  749.                     | (chan = SndChannelPtr(gChan4)));
  750.     END;
  751.  
  752.     FUNCTION CompatibleChan(waveChan: SndChannelPtr): BOOLEAN;
  753.     BEGIN
  754.         CompatibleChan:= (waveChan^.userInfo = waveTableSynth)    {wave or..}
  755.                             | (waveChan^.userInfo = kChanFree);            {free chan}
  756.     END;
  757.  
  758. BEGIN
  759.     ChanAvailable:= noErr;
  760.     IF NOT gNewSndMgr THEN BEGIN
  761.         IF theChan^.nextChan <> NIL THEN BEGIN                            {looks bad}
  762.             ChanAvailable:= badChannel;                                    {prepare to fail}
  763.             IF theChan^.userInfo = waveTableSynth THEN BEGIN        {last attempt}
  764.                 IF IsMyChan(SndChannelPtr(theChan^.nextChan))
  765.                  & CompatibleChan(SndChannelPtr(gChan1))
  766.                  & CompatibleChan(SndChannelPtr(gChan2))
  767.                  & CompatibleChan(SndChannelPtr(gChan3))
  768.                  & CompatibleChan(SndChannelPtr(gChan4))
  769.                     THEN
  770.                         ChanAvailable:= noErr;                                {got lucky}
  771.             END;
  772.         END;
  773.     END;
  774. END;
  775.  
  776. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  777. {$S SoundUnit}
  778. FUNCTION SndDataAvailable(sndHandle: Handle): OSErr;
  779.  
  780. {Given a resource handle, this will attempt to load it into memory.  If the
  781.  data is not available, then return an error.}
  782.  
  783. BEGIN
  784.     SndDataAvailable:= noErr;
  785.     IF sndHandle <> NIL THEN BEGIN
  786.         LoadResource(sndHandle);
  787.         IF sndHandle^ = NIL THEN
  788.             SndDataAvailable:= nilHandleErr;            {master pointer is NIL}
  789.     END ELSE
  790.         SndDataAvailable:= nilHandleErr;                {user passed a NIL handle}
  791. END;
  792.  
  793. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  794. {$S SoundUnit}
  795. FUNCTION HoldSnd(sndHandle: Handle): OSErr;
  796.  
  797. {This is used to put the given sound resource into memory and hold it there.
  798.  I also use a MoveHHi to keep the heap from being fragmented.  If this
  799.  fails, then I return an error.  I dereference the handle and check if the
  800.  master pointer is NIL.  This would mean the data could not be loaded.}
  801.  
  802. BEGIN
  803.     IF sndHandle <> NIL THEN BEGIN
  804.         LoadResource(sndHandle);
  805.         IF sndHandle^ = NIL THEN
  806.             HoldSnd:= nilHandleErr                        {master pointer is NIL}
  807.         ELSE BEGIN
  808.             HoldSnd:= noErr;
  809.             MoveHHi(sndHandle);
  810.             HLock(sndHandle);
  811.         END;
  812.     END ELSE
  813.         HoldSnd:= nilHandleErr;                            {user passed a NIL handle}
  814. END;
  815.  
  816. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  817. {$S SoundUnit}
  818. FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
  819.  
  820. {This routine will return the 'snth' resource ID specified by the sound.
  821.  I use this to determine if the given sound will work with _SndPlay.
  822.  This routine does not require the data to be in memory when called.  It
  823.  also doesn’t lock it down while looking for the information.  I will
  824.  mark the resource as being purgeable in the case the resource attributes
  825.  does not have it’s purgeable bit set.
  826.  
  827.  VERSION 1.1:  If no synth information is found in the snd then by default
  828.  it is assumed to be for the squareWaveSynth.}
  829.  
  830. VAR
  831.     soundPtr:    Ptr;
  832.     theErr:        OSErr;
  833.  
  834. BEGIN
  835.     GetSynthInfo.synthID:= kNoSynth;
  836.     GetSynthInfo.initOption:= 0;
  837.     theErr:= SndDataAvailable(sndHandle);
  838.     IF theErr = noErr THEN BEGIN
  839.         soundPtr:= sndHandle^;
  840.         IF Snd1HdrPtr(soundPtr)^.format = firstSoundFormat THEN BEGIN
  841.             IF Snd1HdrPtr(soundPtr)^.numSynths <> kNoSynth THEN BEGIN
  842.                 soundPtr:= Ptr(ORD4(soundPtr) + SizeOf(Snd1Header));
  843.                 GetSynthInfo.synthID:= SynthInfoPtr(soundPtr)^.synthID;
  844.                 GetSynthInfo.initOption:= SynthInfoPtr(soundPtr)^.initOption;
  845.             END ELSE
  846.                 GetSynthInfo.synthID:= squareWaveSynth;
  847.         END ELSE BEGIN {snd is a format 2 for HyperCard}
  848.             GetSynthInfo.synthID:= sampledSynth;
  849.             GetSynthInfo.initOption:= 0;        {no options currently supported}
  850.         END;
  851.     HPurge(sndHandle);
  852.     END;
  853. END;
  854.  
  855. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  856. {$S SoundUnit}
  857. FUNCTION GetSndDataOffset(sndHandle: Handle;
  858.                                     VAR dataType, waveLength: INTEGER): LONGINT;
  859.  
  860. {This routine will cruise through the given snd resource.  It will locate
  861.  the sound data, if any, and return its type and offset into the resource.
  862.  I prefer to return an offset instead of a pointer because I don’t want
  863.  to have the data locked in memory.  If I return an offset, the caller
  864.  can decide when and if it wants the resource locked down to access the
  865.  sound data.  The first step in finding this data is to determine if I’m
  866.  looking at a format 1 or 2 type snd.  A type 2 is easy, but a type 1 will
  867.  require me to find the number of snths specified and then to skip over
  868.  each one including the init option.  Once this is done, I have a pointer
  869.  to the number of commands in the snd.  When I’ve found the first one, I
  870.  examine it to find out if it is a sound data command.  Being it’s a sound
  871.  resource, the command will also have its dataOffsetFlag set.  Once I’ve
  872.  found a command I’m looking for I return its type and offset, then get out
  873.  of the REPEAT block.  OTHERWISE I go on to the next command.  All of this
  874.  makes it possible to get the sound data for use as an instrument sound.
  875.  Typically this will be a sampled sound.
  876.  
  877.  dataType is the type of data contained in the snd handle.  This would
  878.  be either sampled sound, a wave table, or possibly no snth is specified.
  879.  The later could be used for any of the snth channels.
  880.  
  881.  waveLength is set to the length of the wave table data, if any is present.
  882.  
  883.  WARNING: Do not send this routine a NIL handle.}
  884.  
  885. VAR
  886.     synths,
  887.     howManyCmds:        INTEGER;
  888.     cruisePtr:            Ptr;
  889.  
  890. BEGIN
  891.     GetSndDataOffset:= 0;
  892.     dataType:= kNoSynth;
  893.     waveLength:= 0;
  894.     cruisePtr:= sndHandle^;
  895.     IF cruisePtr <> NIL THEN BEGIN
  896.         IF Snd1HdrPtr(cruisePtr)^.format = firstSoundFormat THEN BEGIN
  897.             synths:= Snd1HdrPtr(cruisePtr)^.numSynths;
  898.             cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd1Header));
  899.             cruisePtr:= Ptr(ORD4(cruisePtr) + (SizeOf(SynthInfo) * synths));
  900.         END ELSE
  901.             cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd2Header));
  902.         howManyCmds:= IntPtr(cruisePtr)^;    {pointing at number of cmds}
  903.         cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(howManyCmds));
  904.  
  905.                                     {cruisePtr is now at the first sound command}
  906.         REPEAT                    {cruise all commands and find a soundCmd or bufferCmd}
  907.             CASE SndCmdPtr(cruisePtr)^.cmd OF
  908.  
  909.             (soundCmd + dataOffsetFlag), (bufferCmd + dataOffsetFlag):
  910.                 BEGIN
  911.                     dataType:= sampledSynth;
  912.                     GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
  913.                     howManyCmds:= 0;                    {done, get out of loop}
  914.                 END;
  915.  
  916.             (waveTableCmd + dataOffsetFlag):
  917.                 BEGIN
  918.                     dataType:= waveTableSynth;
  919.                     waveLength:= SndCmdPtr(cruisePtr)^.param1;
  920.                     GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
  921.                     howManyCmds:= 0;                    {done, get out of loop}
  922.                 END;
  923.  
  924.             OTHERWISE                                    {catch any other type of cmd}
  925.                 BEGIN
  926.                     cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(sndCommand));
  927.                     howManyCmds:= howManyCmds - 1;
  928.                 END;
  929.  
  930.             END;
  931.         UNTIL howManyCmds < 1;                        {done with all the commands}
  932.     END;
  933. END;
  934.  
  935. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  936. {$S SoundUnit}
  937. FUNCTION SupportedSH(sndPtr: SoundHeaderPtr): BOOLEAN;
  938.  
  939. {VERSION 1.1:  Check the given sound header as being supported by the
  940.  running Sound Manager.  The encode fields of the header are tested.
  941.  The Standard encode always works.  The Compressed sound will only work if
  942.  MACE is present.  A MACE sound can also be a stereo sound, which will only
  943.  work on stereo hardware.  The Expanded sound is for a stereo sound and/or
  944.  16bit samples and is only supported by the new Sound Manager.  So far,
  945.  only 8bit samples are supported.  If the sound is a stereo sound, then
  946.  it requires stereo hardware.}
  947.  
  948. BEGIN
  949.     CASE INTEGER(sndPtr^.encode) OF
  950.     
  951.         stdSH:
  952.             SupportedSH:= TRUE;
  953.  
  954.         cmpSH: BEGIN
  955.             IF gHasMACE THEN BEGIN
  956.                 SupportedSH:= (CmpSoundHeaderPtr(sndPtr)^.numChannels = 1)
  957.                                      | ((CmpSoundHeaderPtr(sndPtr)^.numChannels > 1)
  958.                                     & gHasStereo);
  959.             END ELSE
  960.                 SupportedSH:= FALSE;
  961.         END;
  962.         
  963.         extSH: BEGIN
  964.             IF gNewSndMgr THEN BEGIN
  965.                 IF ExtSoundHeaderPtr(sndPtr)^.sampleSize <> 8 THEN
  966.                     SupportedSH:= FALSE
  967.                 ELSE
  968.                     SupportedSH:= (CmpSoundHeaderPtr(sndPtr)^.numChannels = 1)
  969.                                          | ((CmpSoundHeaderPtr(sndPtr)^.numChannels > 1)
  970.                                         & gHasStereo);
  971.             END ELSE
  972.                 SupportedSH:= FALSE;
  973.         END;
  974.  
  975.     END;
  976. END;
  977.  
  978. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  979. {$S SoundUnit}
  980. FUNCTION InstallSampleSnd(myChan: MyChanPtr; sndHandle: Handle): OSErr;
  981.  
  982. {Given a channel and sampled sound resource, this routine will install the
  983.  sound into the channel for use as an instrument.  This allows an
  984.  application to send freqDurationCmds to the channel and play a melody.
  985.  If I sent a bufferCmd instead of the soundCmd, the Sound Manager would play
  986.  the sampled sound.  This is basically what _SndPlay would do with a format 2
  987.  snd.  I insure that I am using only the proper buffer format having the
  988.  standard encode option.  If I were to support compressed sounds, I would
  989.  have to call the MACE synthesizers to expand the buffer before I can use
  990.  it as an instrument.  If I don’t get a sampled sound of standard encoding
  991.  I’ll return a bad format error.  I use _SndDoImmediate to get the sound
  992.  installed because I don’t want this command to be queued.
  993.  
  994.  VERSION 1.1:  Check for a supported sound header.}
  995.  
  996. VAR
  997.     theCmd:             sndCommand;
  998.     dataPtr:            SoundHeaderPtr;
  999.     dataOffset:        LONGINT;
  1000.     sndDataType,
  1001.     ignore:            INTEGER;
  1002.     theErr:            OSErr;
  1003.  
  1004. BEGIN
  1005.     theErr:= HoldSnd(sndHandle);
  1006.     IF theErr = noErr THEN BEGIN
  1007.         dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
  1008.         IF sndDataType = sampledSynth THEN BEGIN
  1009.             dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
  1010.             IF stdSH = INTEGER(dataPtr^.encode) THEN BEGIN
  1011.                 WITH theCmd DO BEGIN
  1012.                     cmd:= soundCmd;
  1013.                     param1:= 0;
  1014.                     param2:= ORD4(dataPtr);
  1015.                 END;
  1016.                 myChan^.dataHandle:= sndHandle;
  1017.                 theErr:= SndDoImmediate(SndChannelPtr(myChan), theCmd);
  1018.             END ELSE
  1019.                 theErr:= badFormat;                            {return a bad format error}
  1020.         END ELSE
  1021.             theErr:= badFormat;                                {return a bad format error}
  1022.         IF theErr <> noErr THEN BEGIN
  1023.             HUnlock(sndHandle);                                {and free up the resource}
  1024.             HPurge(sndHandle);
  1025.         END;
  1026.     END;
  1027.     InstallSampleSnd:= theErr;
  1028. END;
  1029.  
  1030. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1031. {$S SoundUnit}
  1032. FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
  1033.                                                         sndInstrument: Handle): OSErr;
  1034.  
  1035. {This routine will create a sampled sound channel using the INIT option
  1036.  given.  Typically this will be 0.  In any case with System 6.0x this
  1037.  option is ignored by the sampled sound synthesizer.  The given sound
  1038.  resource will be installed into the channel for use as an instrument.
  1039.  
  1040.  WARNING: If the application does not want an instrument sound, then the
  1041.  sndInstrument handle MUST be passed in as NIL.
  1042.  
  1043.  BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
  1044.  a Memory Manager error when allocating its internal buffer.  There is a
  1045.  call to NewPtr(1316) and if a NIL is returned, the Sound Manager will
  1046.  write randomly to low memory.  This can occur when calling _SysBeep under
  1047.  low memory conditions.  Also, this pointer is allocated into the
  1048.  application’s heap instead of the system’s.}
  1049.  
  1050. VAR
  1051.     theErr:        OSErr;
  1052.  
  1053. BEGIN
  1054.     FreeAllChans;
  1055.     theErr:= SndNewChannel(SndChannelPtr(gChan1), sampledSynth,
  1056.                                                                         init, @DoCallBack);
  1057.     IF theErr = noErr THEN BEGIN
  1058.         gChan1^.theChan.userInfo:= sampledSynth;
  1059.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  1060.         IF (theErr = noErr) & (sndInstrument <> NIL) THEN
  1061.             theErr:= InstallSampleSnd(gChan1, sndInstrument);
  1062.     END;
  1063.     IF theErr <> noErr THEN
  1064.         FreeAllChans
  1065.     ELSE
  1066.         gChanOpen:= TRUE;                                        {got an open channel}
  1067.     sampleChan:= SndChannelPtr(gChan1);
  1068.     GetSampleChan:= theErr;
  1069. END;
  1070.  
  1071. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1072. {$S SoundUnit}
  1073. FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
  1074.                             waveLength: INTEGER): OSErr;
  1075.  
  1076. {Given a channel and pointer to a wave table, this will install the wave
  1077.  for use as an instrument into the channel.  If I find the application
  1078.  giving me a NIL pointer, I’ll return an error.  I use _SndDoImmediate
  1079.  to get the sound installed because I don’t want this to be queued.}
  1080.  
  1081. VAR
  1082.     theCmd:        SndCommand;
  1083.  
  1084. BEGIN
  1085.     IF aWavePtr <> NIL THEN BEGIN
  1086.         WITH theCmd DO BEGIN
  1087.             cmd:= waveTableCmd;
  1088.             param1:= waveLength;
  1089.             param2:= ORD4(aWavePtr);
  1090.         END;
  1091.         InstallWave:= SndDoImmediate(waveChan, theCmd);
  1092.     END ELSE
  1093.         InstallWave:= memPCErr;                                {Pointer Check failed}
  1094. END;
  1095.  
  1096. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1097. {$S SoundUnit}
  1098. FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
  1099.                                   waveChan3, waveChan4: SndChannelPtr): OSErr;
  1100.  
  1101. {This will return four wave table channels with their waves installed.
  1102.  When I create a channel I will set the userInfo field marking it with the
  1103.  'snth' associated to it.  This must be done before calling ChanAvailable.
  1104.  Otherwise that test will fail.  If I cannot obtain all four wave channels
  1105.  I will dispose of the ones I did get before returning the error.  This
  1106.  routine expects to find four wave table pointers, or it will fail.}
  1107.  
  1108. VAR
  1109.     theErr:        OSErr;
  1110.  
  1111.     PROCEDURE NewWaveChan(VAR myChan: MyChanPtr; init: INTEGER);
  1112.     BEGIN
  1113.         theErr:= SndNewChannel(SndChannelPtr(myChan), waveTableSynth,
  1114.                                                                         init, @DoCallBack);
  1115.         IF theErr = noErr THEN BEGIN
  1116.             myChan^.theChan.userInfo:= waveTableSynth;
  1117.             theErr:= ChanAvailable(SndChannelPtr(myChan));
  1118.         END;
  1119.     END;
  1120.  
  1121. BEGIN
  1122.     FreeAllChans;
  1123.     NewWaveChan(gChan1, initChan0);
  1124.     IF theErr = noErr THEN BEGIN
  1125.         NewWaveChan(gChan2, initChan1);
  1126.         IF theErr = noErr THEN BEGIN
  1127.             NewWaveChan(gChan3, initChan2);
  1128.             IF theErr = noErr THEN BEGIN
  1129.                 NewWaveChan(gChan4, initChan3);
  1130.             END;
  1131.         END;
  1132.     END;
  1133.     IF theErr <> noErr THEN
  1134.         FreeAllChans                                            {we didn’t make it}
  1135.     ELSE BEGIN
  1136.         waveChan1:= SndChannelPtr(gChan1);
  1137.         waveChan2:= SndChannelPtr(gChan2);
  1138.         waveChan3:= SndChannelPtr(gChan3);
  1139.         waveChan4:= SndChannelPtr(gChan4);
  1140.         gChanOpen:= TRUE;                                        {now we’re making noise}
  1141.     END;
  1142.     GetWaveChans:= theErr;
  1143. END;
  1144.  
  1145. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1146. {$S SoundUnit}
  1147. FUNCTION GetSquareChan(VAR squareChan: SndChannelPtr; timbre: INTEGER): OSErr;
  1148.  
  1149. {This will create a channel for the square wave synthesizer.  When I create a
  1150.  channel I will set the userInfo field marking it with the 'snth'
  1151.  associated to it.  This must be done before calling ChanAvailable.
  1152.  Otherwise that test will fail.  There are no INIT options used by this
  1153.  synthesizer, but I will set the timbre to adjust the tone quality.}
  1154.  
  1155. VAR
  1156.     theErr:        OSErr;
  1157.  
  1158. BEGIN
  1159.     FreeAllChans;
  1160.     theErr:= SndNewChannel(SndChannelPtr(gChan1), squareWaveSynth,
  1161.                                                                 kInitNone, @DoCallBack);
  1162.     IF theErr = noErr THEN BEGIN
  1163.         gChan1^.theChan.userInfo:= squareWaveSynth;
  1164.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  1165.         IF theErr = noErr THEN
  1166.             theErr:= SetSquareTimbre(SndChannelPtr(gChan1), timbre, NOT kWait);
  1167.     END;
  1168.     IF theErr <> noErr THEN
  1169.         FreeAllChans
  1170.     ELSE
  1171.         gChanOpen:= TRUE;                                        {now we’re making noise}
  1172.     squareChan:= SndChannelPtr(gChan1);
  1173.     GetSquareChan:= theErr;
  1174. END;
  1175.  
  1176. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1177. {$S SoundUnit}
  1178. FUNCTION GetNoSynthChan(VAR chan: SndChannelPtr): OSErr;
  1179.  
  1180. {This is the routine to create a channel that isn’t associated with any
  1181.  synthesizer.  Why? Because if you wanted to use _SndPlay asynchronously
  1182.  you need to get such a channel.  When I create a channel I will set the
  1183.  userInfo field marking it with the 'snth' associated to it.  This must be
  1184.  done before calling ChanAvailable.
  1185.  
  1186.  BUG NOTE: Do not use a channel already associated to a snth with
  1187.  _SndPlay.  This causes the Sound Manager to install a second copy of the
  1188.  same snth.}
  1189.  
  1190. VAR
  1191.     theErr:        OSErr;
  1192.  
  1193. BEGIN
  1194.     FreeAllChans;
  1195.     theErr:= SndNewChannel(SndChannelPtr(gChan1), kNoSynth,
  1196.                                                                 kInitNone, @DoCallBack);
  1197.     IF theErr = noErr THEN BEGIN
  1198.         gChan1^.theChan.userInfo:= kNoSynth;
  1199.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  1200.     END;
  1201.     IF theErr <> noErr THEN
  1202.         FreeAllChans
  1203.     ELSE
  1204.         gChanOpen:= TRUE;                                        {now we’re making noise}
  1205.     chan:= SndChannelPtr(gChan1);
  1206.     GetNoSynthChan:= theErr;
  1207. END;
  1208.  
  1209. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1210. {$S SoundUnit}
  1211. FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
  1212.  
  1213. {This routine will use the given channel and snd resource with _SndPlay.
  1214.  This is used to play a sound, which is a series of sound commands commonly
  1215.  referred to as a sequence.  First thing I do is make sure the song fits in
  1216.  memory.  _SndPlay will lock this resource in memory and then pump the snd
  1217.  for all of its worth.  I am calling it asynchronously, and if I was using
  1218.  a snd that contained sound data I wouldn’t mark the snd as being
  1219.  purgeable.  But in this case, _SndPlay will be done with the snd as soon
  1220.  as it returns because it copied all of the commands into the channel.
  1221.  (There’s no data associated with a sequence, just commands.)  _SndPlay
  1222.  will not return until it has done so.  After _SndPlay I need to work
  1223.  around a bug in the freqDurationCmd.  The last thing to do is to send a
  1224.  callBackCmd to signal me that the channel has completed.  If any Sound
  1225.  Manager errors are encountered, I return them to the application.  If the
  1226.  application passed me a NIL snd handle, I’ll return an error.
  1227.  
  1228.  WARNING: Make sure you are using a snd that only has frequency type commands
  1229.  in it and not something such as a bufferCmd.
  1230.  
  1231.  BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
  1232.  The sound will continue to be heard, looping forever, until a quietCmd is sent
  1233.  or the channel is disposed of.  To prevent unwanted looping, I send a
  1234.  quietCmd after all frequency commands.  Also read a related bug note when
  1235.  disposing of channels in the routine FreeChan.}
  1236.  
  1237. VAR
  1238.     theErr:            OSErr;
  1239.  
  1240. BEGIN
  1241.     theErr:= SndDataAvailable(sndSong);                {get the data loaded}
  1242.     IF theErr = noErr THEN BEGIN
  1243.         theErr:= SndPlay(chan, SndListHandle(sndSong), kSMAsynch);    {pump the sound}
  1244.         HUnlock(sndSong);
  1245.         HPurge(sndSong);
  1246.         IF theErr = noErr THEN BEGIN
  1247.             theErr:= SendQuiet(chan, kWait);            {work around bug}
  1248.             IF theErr = noErr THEN
  1249.                 theErr:= SoundComplete(chan);
  1250.         END;
  1251.     END ELSE
  1252.         PlaySong:= nilHandleErr;                        {snd data was not available}
  1253.     IF theErr <> noErr THEN
  1254.         FreeAllChans;
  1255.     PlaySong:= theErr;
  1256. END;
  1257.  
  1258. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1259. {$S SoundUnit}
  1260. FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
  1261.  
  1262. {This is used to send a syncCmd to a channel and causes the other channels
  1263.  that are being held by a synchCmd to be released.  Of course, this assumes
  1264.  the application has already called SynchChans.  _SndDoImmediate is used
  1265.  to get the command directly to the synthesizer bypassing the queue.
  1266.  
  1267.  BUG NOTE: I’ve found that immediately clearing the channels and starting
  1268.  new ones may cause the channels to startup playing out of synch?  This
  1269.  happens while disposing the wave channels and starting them immediately.}
  1270.  
  1271. VAR
  1272.     theCmd:             sndCommand;
  1273.  
  1274. BEGIN
  1275.     WITH theCmd DO BEGIN
  1276.         cmd:= syncCmd;
  1277.         param1:= 1;
  1278.         param2:= kSyncID;
  1279.     END;
  1280.     ReleaseSynch:= SndDoImmediate(chan, theCmd);
  1281. END;
  1282.  
  1283. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1284. {$S SoundUnit}
  1285. FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
  1286.  
  1287. {This is used to synchronize four wave table channels.  By first sending
  1288.  the synchCmd, I can send a sequence of other commands to the channel and
  1289.  not have the channel attempt to start processing any of them.  That is until
  1290.  another synchCmd is sent causing all of the previous synchCmd’s counter
  1291.  to be decremented.  After getting all the channels in synch and sending
  1292.  the sequence of further commands, then use the ReleaseSynch routine to
  1293.  start all of the channels processing their respective queues.
  1294.  _SndDoImmediate is used to get the command directly to the synthesizer
  1295.  bypassing the queue.}
  1296.  
  1297. VAR
  1298.     theCmd:             SndCommand;
  1299.     theErr:            OSErr;
  1300.  
  1301.     PROCEDURE Synch1Chan(chan: SndChannelPtr; count: INTEGER);
  1302.     BEGIN
  1303.         WITH theCmd DO BEGIN
  1304.             cmd:= syncCmd;
  1305.             param1:= count;
  1306.             param2:= kSyncID;
  1307.         END;
  1308.         theErr:= SndDoImmediate(chan, theCmd);
  1309.     END;
  1310.  
  1311. BEGIN
  1312.     Synch1Chan(chan4, 5);
  1313.     IF theErr = noErr THEN BEGIN
  1314.         Synch1Chan(chan3, 4);
  1315.         IF theErr = noErr THEN BEGIN
  1316.             Synch1Chan(chan2, 3);
  1317.             IF theErr = noErr THEN BEGIN
  1318.                 Synch1Chan(chan1, 2);
  1319.             END;
  1320.         END;
  1321.     END;
  1322.     SynchChans:= theErr;
  1323. END;
  1324.  
  1325. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1326. {$S SoundUnit}
  1327. FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
  1328.                             song1, song2, song3, song4: Handle): OSErr;
  1329.  
  1330. {In order to synchronize channels, the synchCmd is needed.  Once all of the
  1331.  song has been sent into each channel, a final synchCmd is issued to
  1332.  release them.  Don’t send more commands into a channel that it can hold at
  1333.  one time while the channel is in synch mode.}
  1334.  
  1335. VAR
  1336.     theErr:            OSErr;
  1337.  
  1338. BEGIN
  1339.     theErr:= SynchChans(waveChan1, waveChan2, waveChan3, waveChan4);
  1340.     IF theErr = noErr THEN BEGIN
  1341.     theErr:= PlaySong(waveChan1, song1);
  1342.     IF theErr = noErr THEN BEGIN
  1343.         theErr:= PlaySong(waveChan2, song2);
  1344.         IF theErr = noErr THEN BEGIN
  1345.             theErr:= PlaySong(waveChan3, song3);
  1346.             IF theErr = noErr THEN BEGIN
  1347.                 theErr:= PlaySong(waveChan4, song4);
  1348.                 IF theErr = noErr THEN
  1349.                     theErr:= ReleaseSynch(waveChan1);
  1350.                 END;
  1351.             END;
  1352.         END;
  1353.     END;
  1354.     Play4Waves:= theErr;
  1355. END;
  1356.  
  1357. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1358. {$S SoundUnit}
  1359. FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
  1360.  
  1361. {WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE.  I’ve provided
  1362.  this routine because people have asked me how HyperCard performs its PLAY
  1363.  command and why their HyperCard sounds do not sound right using the
  1364.  _SndPlay routine.  The correct answer is that _SndPlay plays the sound
  1365.  correctly.  HyperCard is attempting to change the frequency by adjusting
  1366.  the sample rate.  This is NOT the correct approach.  Define the sound
  1367.  buffer as it should be played.  _SndPlay plays the sound as it is defined.
  1368.  If the result from _SndPlay is not what you want, then it is the sample
  1369.  that is incorrect and should be edited.  The sample rate is the rate at
  1370.  which the sound was recorded.  If you didn’t record it, then how do you
  1371.  know what’s the correct rate?  Set the baseFrequency to the frequency that
  1372.  was recorded.  If you recorded middle C at 22k, then the rate is 22k and the
  1373.  baseFrequency is middle C.  HyperCard is incorrect in using the sample rate
  1374.  as the frequency of the sound.  Furthermore, using this technique of
  1375.  calculating a new sample rate can introduce errors.  The resulting sample
  1376.  rate will not be the proper pitch.  Also, the sample rate for high pitches
  1377.  will be very inaccurate and impossible for the Mac to reproduce.  Such a
  1378.  problem can happen if the given sample rate was 22k and is to be played
  1379.  back at three octaves higher.  Even 44k samples transposed up a half
  1380.  octave will fail.  Using the soundCmd and freqDurationCmd will not have
  1381.  this problem.
  1382.  
  1383.  Given a sound resource, this routine will play it in the manner that
  1384.  HyperCard does.  HyperCard assumes that a sound is to be played at middle
  1385.  C when the user does not specify a frequency value in the PLAY command.  I
  1386.  don’t know why.  (What’s middle C when I want to hear speech or the sound
  1387.  of crickets?)  At any rate (pun intended), I get a sampled sound channel.
  1388.  I find the sound data offset in the resource, which has to be locked down
  1389.  at this time.  Once I have the sound data, I get its original sample rate.
  1390.  I have to calculate what a new sample rate would be based on its baseFrequency.
  1391.  The baseFrequency is the frequency at which the sound was recorded.  I’m
  1392.  not sure what this means to crickets, but if this is set to middle C then
  1393.  HyperCard doesn’t attempt to modify the sample rate.  (If you’re wondering
  1394.  how the math works in this routine, buy a book on music theory.  I’m here to
  1395.  provide Mac support.)  Once I’ve adjusted the sample rate, I use the
  1396.  bufferCmd to play it.  Then I restore the sound resource to its original
  1397.  state.  If I didn’t do this it would be possible that the resource was
  1398.  still in memory the next time I use it having the adjusted sample rate.
  1399.  This would cause me to incorrectly adjust it again.  Unlike HyperCard, I
  1400.  can do this for both a format 1 and 2.
  1401.  
  1402.  BUG NOTE: Do not call SANE of the FPU while the Sound Manager is running.
  1403.  Refer to Tech Note #235.  This problem was fixed in the new Sound Manager.
  1404.  
  1405.  VERSION 1.1:  Replaced the previous test of the sound header's encode
  1406.  value.  The previous version was incorrect.  The bufferCmd will automatically
  1407.  de-code a MACE compressed sound if MACE is available.  Otherwise, the sound
  1408.  will not be able to be used.  So, a new routine is being used to check for
  1409.  supported sound headers.  The conflict with the Sound Manager and SANE was
  1410.  resolved in the new Sound Manager.  It no longers uses extended numbers, and
  1411.  instead uses fixed math.}
  1412.  
  1413. VAR
  1414.     theCmd:                 SndCommand;
  1415.     newRate:                Extended;
  1416.     oldRate:                Fixed;
  1417.     dataPtr:                SoundHeaderPtr;
  1418.     dataOffset:            LONGINT;
  1419.     sndDataType,
  1420.     power, ignore:        INTEGER;
  1421.     theErr:                OSErr;
  1422.     pextended:            Extended;
  1423.     index:                INTEGER;
  1424.  
  1425. BEGIN
  1426.     theErr:= HoldSnd(sndHandle);
  1427.     IF theErr = noErr THEN BEGIN
  1428.         theErr:= GetSampleChan(SndChannelPtr(gChan1), kInitNone, NIL);
  1429.         gChan1^.dataHandle:= sndHandle;        {so FreeAllChans can dispose of data}
  1430.         IF theErr = noErr THEN BEGIN
  1431.  
  1432.             dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
  1433.             IF sndDataType = sampledSynth THEN BEGIN
  1434.                 dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
  1435.                 IF SupportedSH(dataPtr) THEN BEGIN
  1436.                     oldRate:= dataPtr^.sampleRate;            {save original sample rate}
  1437.                     IF NOT (INTEGER(dataPtr^.baseFrequency) = kMiddleC) THEN BEGIN
  1438.                         IF dataPtr^.sampleRate < 0 THEN         {large positive number}
  1439.                             newRate:= (Fix2X(BAnd(dataPtr^.sampleRate, maxLongInt))
  1440.                                             + maxLongInt + 1)        {twos comp. is off by one}
  1441.                         ELSE
  1442.                             newRate:= Fix2X(dataPtr^.sampleRate);
  1443.                         newRate:= Fix2X(dataPtr^.sampleRate);
  1444.                         power:= kMiddleC - INTEGER(dataPtr^.baseFrequency);
  1445.                         pextended := 1.0;
  1446.                         FOR index:= 1 TO power DO BEGIN
  1447.                             pextended := pextended * twelfthRootTwo
  1448.                         END;
  1449.                         dataPtr^.sampleRate:= X2Fix(pextended * newRate);
  1450.                     END;
  1451.                     WITH theCmd DO BEGIN
  1452.                         cmd:= bufferCmd;
  1453.                         param1:= 0;
  1454.                         param2:= ORD4(dataPtr);
  1455.                     END;
  1456.                     theErr:= SndDoImmediate(SndChannelPtr(gChan1), theCmd);
  1457.                     IF theErr = noErr THEN
  1458.                         theErr:= SoundComplete(SndChannelPtr(gChan1));
  1459.                     dataPtr^.sampleRate:= oldRate;        {restore original sample rate}
  1460.                 END ELSE
  1461.                     theErr:= badFormat;                        {not SupportedSH}
  1462.             END ELSE
  1463.                 theErr:= badFormat;                            {sndDataType not sampledSynth}
  1464.         END;
  1465.     END;
  1466.     IF theErr <> noErr THEN
  1467.         FreeAllChans;
  1468.     HyperSndPlay:= theErr;
  1469. END;
  1470.  
  1471. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1472. {$S SoundUnit}
  1473. FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
  1474.  
  1475. {Given a sound resource, this routine will call _SndPlay.  The snd must
  1476.  be either a format 2 or format 1 that contains snth information.
  1477.  Using _SndPlay asynchronously requires us to lock the snd prior to
  1478.  calling the trap.  The reason being is _SndPlay remembers the state of the
  1479.  lock bit using _HGetState and _HSetState.  If the snd is unlocked when
  1480.  it’s passed to _SndPlay, it will be unlocked again when _SndPlay exits.
  1481.  This would be bad when using the sound asynchronously.  If the sound being
  1482.  passed in happens to be a compressed sound created with MACE, it will “do
  1483.  the right thing.”  If MACE isn’t around the Sound Manager will pretend to
  1484.  play a sound but nothing will be heard.
  1485.  
  1486.  BUG NOTE:  The sampled sound synthesizer in System 6.0x does not check for
  1487.  a Memory Manager error when allocating its internal buffer.  There is a
  1488.  call to NewPtr(1316) and if a NIL is return, the Sound Manager will write
  1489.  randomly to memory.  Also, the pointer is allocated into the application’s
  1490.  heap instead of the system’s.
  1491.  
  1492.  BUG NOTE:  _SndPlay when using System 6.0.4 and a sampled sound will send
  1493.  a bogus callBackCmd into the channel.  This will cause the user’s call
  1494.  back procedure to be called as soon as the sound has completed.  Refer
  1495.  to the DoCallBack routine for details.
  1496.  
  1497.  VERSION 1.1:  Add the check for the sound header being supported and 
  1498.  replaced the check of the snth information.  No synth information in the
  1499.  snd is valid and would mean to play the snd using the squareWaveSynth.}
  1500.  
  1501. VAR
  1502.     dataPtr:                SoundHeaderPtr;
  1503.     dataOffset:            LONGINT;
  1504.     sndDataType,
  1505.     ignore:                INTEGER;
  1506.     theErr:                OSErr;
  1507.  
  1508. BEGIN
  1509.     theErr:= HoldSnd(sndHandle);                {hold on to the sound}
  1510.     IF theErr = noErr THEN BEGIN
  1511.         theErr:= GetNoSynthChan(SndChannelPtr(gChan1));
  1512.         gChan1^.dataHandle:= sndHandle;        {so FreeAllChans can dispose of data}
  1513.         IF theErr = noErr THEN BEGIN
  1514.             gChan1^.theChan.userInfo:= GetSynthInfo(sndHandle).synthID;
  1515.             dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
  1516.             IF sndDataType = sampledSynth THEN BEGIN
  1517.                 dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
  1518.                 IF NOT SupportedSH(dataPtr) THEN
  1519.                     theErr:= badFormat;
  1520.             END;
  1521.             IF theErr = noErr THEN BEGIN
  1522.                 theErr:= SndPlay(SndChannelPtr(gChan1), SndListHandle(sndHandle), kSMAsynch);
  1523.                 IF theErr = noErr THEN
  1524.                     theErr:= SoundComplete(SndChannelPtr(gChan1));
  1525.             END;
  1526.         END;
  1527.     END;
  1528.     IF theErr <> noErr THEN
  1529.         FreeAllChans;
  1530.     AsynchSndPlay:= theErr;
  1531. END;
  1532.  
  1533.  
  1534.  
  1535. END. {UNIT}
  1536.